package com.gitee.fanx.retty;

import com.gitee.fanx.retty.exception.NoRouteFoundException;
import com.gitee.fanx.retty.web.*;
import io.netty.buffer.*;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Http请求处理
 *
 * @author fanhang
 */
final class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

	private final static Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);

	private ObjectMapper objectMapper = new ObjectMapper();
	private RouteMapping routeMapping;
	private ResponseHandler responseHandler;
	private ExceptionHandler exceptionHandler;

	public HttpRequestHandler(RouteMapping routeMapping, ResponseHandler responseHandler, ExceptionHandler exceptionHandler) {
		this.routeMapping = routeMapping;
		this.responseHandler = responseHandler;
		this.exceptionHandler = exceptionHandler;
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) {
		ctx.flush();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		logger.error(cause.getMessage());
		ctx.close();
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
		FullHttpResponse httpResponse = null;
		ByteBuf data = Unpooled.buffer();
		ExceptionResult exceptionResult = null;
		try {
			Object result = this.handler(ctx, httpRequest);
			data = Unpooled.buffer().writeBytes(objectMapper.writeValueAsBytes(result));
			httpResponse = new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK, data);
		} catch (NoRouteFoundException e) {
			exceptionResult = this.exceptionHandler.handle(e);
		} catch (Throwable e) {
			exceptionResult = this.exceptionHandler.handle(e);
			if (exceptionResult == null) {
				exceptionResult = new ExceptionResult(HttpResponseStatus.INTERNAL_SERVER_ERROR, null);
			}
		}
		if (exceptionResult != null) {
			data.writeBytes(objectMapper.writeValueAsBytes(exceptionResult.getBody()));
			httpResponse = new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.valueOf(exceptionResult.getCode()), data);
		}
		httpResponse.headers().add("content-type", "application/json; charset=UTF-8");
		ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
	}

	private Object handler(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Throwable {
		Request request = new Request(httpRequest);
		Route route = routeMapping.match(request.method(), request.path());
		if (route == null) {
			logger.debug("request [{}] route note found", httpRequest.uri());
			throw new NoRouteFoundException(request);
		}
		logger.debug("request [{}] route: {}", request.path(), route);
		Object result = route.handle(request);
		if (this.responseHandler == null) {
			return result;
		}
		return this.responseHandler.handle(request, route, result);
	}

}
